/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using EffiProzDB.Lib;





namespace EffiProzDB.Persist
{

    /**
     * New NIO version of ScaledRAFile. This class is used only for storing a CACHED
     * TABLE .data file and cannot be used for TEXT TABLE source files.
     *
     * Due to various issues with java.nio classes, this class will use a mapped
     * channel of fixed size. After reaching this size, the file and channel are
     * closed.
     *
     * @author fredt@users
     * @version  1.8.0.5
     * @since 1.8.0.5
     */
    class ScaledRAFileNIO : ScaledRAInterface
    {

        private bool readOnly;
        private long bufferLength;
        private FileStream file = null;
        private MemoryMappedViewAccessor buffer;
        private MemoryMappedFile channel;
        private bool bufferModified;
        private SimpleLog appLog;
        private const string CLR_ERROR = "CLR threw unsupported Exception";
        private long pos = 0;

        public ScaledRAFileNIO(Database database, string name, bool readOnly,
                        int bufferLength)
        {

            long fileLength;

            if (bufferLength < 1 << 18)
            {
                bufferLength = 1 << 18;
            }

            try
            {
                file = new FileStream(name, FileMode.OpenOrCreate, readOnly ? FileAccess.Read :
                      FileAccess.ReadWrite);

            }
            catch (Exception e)
            {
                throw e;
            }

            try
            {
                fileLength = file.Length;
            }
            catch (Exception e)
            {
                file.Close();

                throw e;
            }

            if (fileLength > ScaledRAFile.MAX_NIO_LENGTH)
            {
                file.Close();

                throw new IOException("length exceeds nio limit");
            }

            if (bufferLength < fileLength)
            {
                bufferLength = (int)fileLength;
            }

            bufferLength =  newNIOBufferSize(bufferLength);

            if (readOnly)
            {
                bufferLength = (int)fileLength;
            }

            if (fileLength < bufferLength)
            {
                try
                {
                    file.Seek(bufferLength - 1, SeekOrigin.Begin);
                    file.WriteByte(0);
                    file.Flush();
                    file.Close();

                    file = new FileStream(name, FileMode.OpenOrCreate, readOnly ? FileAccess.Read :
                      FileAccess.ReadWrite);

                }
                catch (Exception e)
                {
                    file.Close();

                    throw e;
                }
            }

            this.appLog = database.logger.appLog;
            this.readOnly = readOnly;
            this.bufferLength = bufferLength;


            try
            {

                this.channel = MemoryMappedFile.CreateFromFile(file);

                buffer = channel.CreateViewAccessor(0, bufferLength, readOnly ? MemoryMappedFileAccess.Read :
                    MemoryMappedFileAccess.ReadWrite);

                Trace.printSystemOut("NIO file instance created. mode: "
                                     + readOnly);

                if (!readOnly)
                {
                    long tempSize = bufferLength - fileLength;

                    if (tempSize > 1 << 18)
                    {
                        tempSize = 1 << 18;
                    }

                    byte[] temp = new byte[(int)tempSize];

                    try
                    {
                        long pos = fileLength;

                        for (; pos < bufferLength - tempSize; pos += tempSize)
                        {

                            buffer.WriteArray(pos, temp, 0, temp.Length);

                        }

                        buffer.WriteArray(pos, temp, 0, (int)(bufferLength - pos));
                        buffer.Flush();

                    }
                    catch (Exception t)
                    {
                        appLog.logContext(t, CLR_ERROR + " " + "length: "
                                          + bufferLength);
                    }

                    this.pos = 0; //buffer.position(0);
                }
            }
            catch (Exception e)
            {
                Trace.printSystemOut("NIO constructor failed:  " + bufferLength);

                buffer = null;
                channel = null;

                file.Close();
                System.GC.Collect();

                throw e;
            }
        }

        public long length()
        {

            try
            {
                return file.Length;
            }
            catch (IOException e)
            {
                appLog.logContext(e, "nio");

                throw e;
            }
            catch (Exception e)
            {
                appLog.logContext(e, CLR_ERROR);

                throw new IOException(e.ToString());
            }
        }

        public void seek(long newPos)
        {

            pos = newPos;
            //try
            //{
            //    buffer.position((int)newPos);
            //}
            //catch (ArgumentException e)
            //{
            //    appLog.logContext(e, "nio");

            //    throw new IOException(e.ToString());
            //}
            //catch (Exception e)
            //{
            //    appLog.logContext(e, JVM_ERROR);

            //    throw new IOException(e.ToString());
            //}
        }

        public long getFilePointer()
        {

            return pos;
            //try {
            //    return buffer.position();
            //} catch (Exception e) {
            //    appLog.logContext(e, JVM_ERROR);

            //    throw new IOException(e.ToString());
            //}
        }

        public int read()
        {

            try
            {
                return buffer.ReadByte(pos);
            }
            catch (Exception e)
            {
                appLog.logContext(e, CLR_ERROR);

                throw new IOException(e.ToString());
            }
        }

        public void read(byte[] b, int offset, int length)
        {

            try
            {
                buffer.ReadArray(pos, b, offset, length);
            }
            catch (Exception e)
            {
                appLog.logContext(e, CLR_ERROR);

                throw new IOException(e.ToString());
            }
        }

        public int readInt()
        {

            try
            {
                return buffer.ReadInt32(pos);
            }
            catch (Exception e)
            {
                appLog.logContext(e, CLR_ERROR);

                throw new IOException(e.ToString());
            }
        }

        public long readLong()
        {

            try
            {
                return buffer.ReadInt64(pos);
            }
            catch (Exception e)
            {
                appLog.logContext(e, CLR_ERROR);

                throw new IOException(e.ToString());
            }
        }

        public void write(byte[] b, int offset, int len)
        {

            try
            {
                bufferModified = true;

                buffer.WriteArray(pos, b, offset, len);
            }
            catch (Exception e)
            {
                appLog.logContext(e, CLR_ERROR);

                throw new IOException(e.ToString());
            }
        }

        public void writeInt(int i)
        {

            try
            {
                bufferModified = true;

                buffer.Write(pos, (int)i);
            }
            catch (Exception e)
            {
                appLog.logContext(e, CLR_ERROR);

                throw new IOException(e.ToString());
            }
        }

        public void writeLong(long i)
        {

            try
            {
                bufferModified = true;

                buffer.Write(pos, (long)i);
            }
            catch (Exception e)
            {
                appLog.logContext(e, CLR_ERROR);

                throw new IOException(e.ToString());
            }
        }

        public void close()
        {

            try
            {
                Trace.printSystemOut("NIO close() start - fileLength = "
                                     + bufferLength);

                if (buffer != null && bufferModified)
                {
                    try
                    {
                        buffer.Flush();
                    }
                    catch (Exception t)
                    {
                        try
                        {
                            buffer.Flush();
                        }
                        catch (Exception)
                        {
                            appLog.logContext(t, CLR_ERROR + " " + "length: "
                                              + bufferLength);
                        }
                    }
                }

                buffer = null;
                channel = null;

                file.Close();
                System.GC.Collect();
            }
            catch (Exception e)
            {
                appLog.logContext(e, "length: " + bufferLength);

                throw new IOException(e.ToString());
            }
        }

        public bool isReadOnly()
        {
            return readOnly;
        }

        public bool wasNio()
        {
            return true;
        }

        public bool canAccess(int length)
        {
            return pos + length <= bufferLength;
        }

        public bool canSeek(long position)
        {
            return position <= bufferLength;
        }

        public Database getDatabase()
        {
            return null;
        }

        static int newNIOBufferSize(int newSize)
        {

            int bufSize = 0;

            for (int scale = 20; scale < 30; scale++)
            {
                bufSize = 1 << scale;

                if (bufSize >= newSize)
                {
                    break;
                }
            }

            return bufSize;
        }
    }
}
